package com.xjk.xml;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

import org.apache.commons.pool.KeyedObjectPool;
import org.apache.commons.pool.KeyedPoolableObjectFactory;
import org.apache.commons.pool.impl.GenericKeyedObjectPool;

/**
 * 线程安全的xml转换
 */
public class MarshallerUtil {


    public static final byte WHEN_EXHAUSTED_FAIL = 0;

    public static final byte WHEN_EXHAUSTED_BLOCK = 1;

    public static final byte WHEN_EXHAUSTED_GROW = 2;

    public static final int DEFAULT_MAX_IDLE = 200;

    public static final int DEFAULT_MAX_ACTIVE = 2000;

    public static final int DEFAULT_MAX_TOTAL = -1;

    public static final byte DEFAULT_WHEN_EXHAUSTED_ACTION = WHEN_EXHAUSTED_BLOCK;

    public static final long DEFAULT_MAX_WAIT = -1L;

    public static final boolean DEFAULT_TEST_ON_BORROW = false;

    public static final boolean DEFAULT_TEST_ON_RETURN = false;

    public static final boolean DEFAULT_TEST_WHILE_IDLE = false;

    public static final long DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS = -1L;

    public static final int DEFAULT_NUM_TESTS_PER_EVICTION_RUN = 3;

    public static final long DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS = 1000L * 60L * 30L;

    public static final int DEFAULT_MIN_IDLE = 0;

    public static final boolean DEFAULT_LIFO = true;

    protected static KeyedObjectPool marPool = new GenericKeyedObjectPool(new JaxbMarshallerFactory());

    protected static KeyedObjectPool unmarPool = new GenericKeyedObjectPool(
            new JaxbUnmarshallerFactory(),
            DEFAULT_MAX_ACTIVE,
            DEFAULT_WHEN_EXHAUSTED_ACTION,
            DEFAULT_MAX_WAIT,
            DEFAULT_MAX_IDLE,
            DEFAULT_TEST_ON_BORROW,
            DEFAULT_TEST_ON_RETURN,
            DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS,
            DEFAULT_NUM_TESTS_PER_EVICTION_RUN,
            DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS,
            DEFAULT_TEST_WHILE_IDLE);

    /**
     * 利用JAXB将XML解析成对象
     *
     * @param clazz
     * @param xml
     * @return
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public static <T> T buildJaxbSafe(Class<T> clazz, String xml) {
        Unmarshaller unmarshaller = null;
        T object = null;
        Object o = null;
        try {
            // 从线程池中借出
            unmarshaller = (Unmarshaller) unmarPool.borrowObject(clazz);
            o = unmarshaller.unmarshal(new StringReader(xml));
            if (o instanceof JAXBElement) {
                JAXBElement<?> element = (JAXBElement<?>) o;
                object = (T) element.getValue();
            } else {
                object = (T) unmarshaller.unmarshal(new StringReader(xml));
            }
        } catch (Exception e) {
            throw new RuntimeException("XML转换失败", e);
        } finally {
            // 归还到对象池
            try {
                unmarPool.returnObject(clazz, unmarshaller);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return object;
    }

    /**
     * 利用JAXB将XML解析成对象
     *
     * @param clazz
     * @param xml
     * @return
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public static <T> T buildJaxbSafe(Class<T> clazz, File file) throws RuntimeException {
        Unmarshaller unmarshaller = null;
        T object = null;
        Object o = null;
        try {
            // 从线程池中借出
            unmarshaller = (Unmarshaller) unmarPool.borrowObject(clazz);
            o = unmarshaller.unmarshal(file);
            if (o instanceof JAXBElement) {
                JAXBElement<?> element = (JAXBElement<?>) o;
                object = (T) element.getValue();
            } else {
                object = (T) unmarshaller.unmarshal(file);
            }
        } catch (Exception e) {

            throw new RuntimeException("XML转换失败", e);
        } finally {
            // 归还到对象池
            try {
                unmarPool.returnObject(clazz, unmarshaller);
            } catch (Exception e) {

            }
        }
        return object;
    }

    /**
     * 利用JAXB将对象解析成XML
     *
     * @param object
     * @return
     * @throws Exception
     */
    public static String buildJaxbSafe(Object object) throws RuntimeException {
        return buildJaxbSafe(object, Boolean.TRUE);
    }

    @SuppressWarnings("unchecked")
    public static String buildJaxbSafe(Object object, Boolean format) throws RuntimeException {
        String result = null;
        Marshaller marshaller = null;
        try {
            Class<?> clazz = object.getClass();
            marshaller = (Marshaller) marPool.borrowObject(clazz);
            marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, format);
            marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            marshaller.marshal(object, os);
            result = new String(os.toByteArray(), "UTF-8");
        } catch (Exception e) {
            throw new RuntimeException("XML转换失败", e);
        } finally {
            // 归还到对象池
            if (object != null) {
                try {
                    marPool.returnObject(object.getClass(), marshaller);
                } catch (Exception e) {

                }
            }
        }
        return result;
    }
}

class JaxbUnmarshallerFactory extends JaxbMarshallerFactory {

    @Override
    public synchronized Object makeObject(Object key) throws Exception {
        Class<?> clazz = (Class<?>) key;
        if (!contextMap.containsKey(key)) {
            JAXBContext context = JAXBContext.newInstance(clazz);
            contextMap.put(clazz, context);
        }
        return contextMap.get(clazz).createUnmarshaller();
    }
}

class JaxbMarshallerFactory implements KeyedPoolableObjectFactory {

    protected Map<Class<?>, JAXBContext> contextMap = new HashMap<Class<?>, JAXBContext>();

    @Override
    public synchronized Object makeObject(Object key) throws Exception {
        Class<?> clazz = (Class<?>) key;
        if (!contextMap.containsKey(clazz)) {
            JAXBContext context = JAXBContext.newInstance(clazz);
            contextMap.put(clazz, context);
        }
        return contextMap.get(clazz).createMarshaller();
    }

    @Override
    public synchronized void destroyObject(Object key, Object obj) throws Exception {
        contextMap.remove(key);
    }

    @Override
    public boolean validateObject(Object key, Object obj) {
        return true;
    }

    @Override
    public void activateObject(Object key, Object obj) throws Exception {
        // System.out.println("user "+Thread.currentThread()+" borrow "+obj.hashCode());
    }

    @Override
    public void passivateObject(Object key, Object obj) throws Exception {
        // System.out.println("user "+Thread.currentThread()+" return "+obj.hashCode());
    }
}